Skip to content

[pull] main from MetaMask:main#782

Merged
pull[bot] merged 6 commits into
Reality2byte:mainfrom
MetaMask:main
May 25, 2026
Merged

[pull] main from MetaMask:main#782
pull[bot] merged 6 commits into
Reality2byte:mainfrom
MetaMask:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 25, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

tommasini and others added 6 commits May 25, 2026 11:53
<!--
Please submit this PR as a draft initially.

Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.

In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->

## **Description**
Bump qs 15.2
<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->

## **Changelog**

<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`

If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`

(This helps the Release Engineer do their job more quickly and
accurately)
-->

CHANGELOG entry:

## **Related issues**

Fixes:

## **Manual testing steps**

```gherkin
Feature: my feature name

  Scenario: user [verb for user action]
    Given [describe expected initial app state]

    When user [verb for user action]
    Then [describe expected outcome]
```

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

<!--
Every checklist item must be consciously assessed before marking this PR
as
"Ready for review". A checked box means you deliberately considered that
responsibility, not that you literally performed every action listed.

Unchecked boxes are ambiguous: they are not an implicit "N/A" and they
are not
a silent "skip". See `docs/readme/ready-for-review.md` for the full
checklist
semantics.
-->

- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I've included tests if applicable
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).

## **Pre-merge reviewer checklist**

<!--
Reviewer checklist items follow the same semantics as the author
checklist: an
unchecked box is ambiguous, a checked box means the reviewer consciously
assessed that responsibility. See `docs/readme/ready-for-review.md`.
-->

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Dependency-only patch with no app code changes; `qs` is a transitive
query-string parser, so blast radius is limited to how dependents
serialize/parse URLs.
> 
> **Overview**
> Upgrades the **`qs`** query-string library from **6.14.1** to
**6.15.2** across Yarn resolutions, direct `package.json` dependencies,
and **`yarn.lock`**. No application source files change—only dependency
pins and lockfile metadata (version, resolution checksum).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ea4207a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…16 KB warning (#30590)

## **Description**

Google Play flagged two prebuilt `.so` files in our production AAB as
non-compliant with Android's 16 KB page-size requirement, both in the
`x86_64` ABI:

- `base/lib/x86_64/libconceal.so` — from `react-native-keychain` via
`com.facebook.conceal:1.1.3` (last released in 2016, archived upstream).
- `base/lib/x86_64/libsecp256k1.so` — from `react-native-fast-crypto`,
shipped as an `IMPORTED` prebuilt in its CMake config (not rebuilt from
source).

Neither library can be realistically rebuilt with 16 KB alignment:

- Facebook Conceal has been unmaintained since 2018; there is no
source-of-truth fork with 16 KB alignment.
- `react-native-fast-crypto` consumes `libsecp256k1.so` as a prebuilt
binary; the existing yarn patch already adds
`-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON` for the bridge code we
compile, but it does not regenerate the imported `libsecp256k1.so`
files.

x86_64 is only ever used by Chromebook ARC++/ARCVM and emulators — no
real phone ships with an x86_64 CPU. Dropping the x86_64 ABI from
production AABs silences the Play warning without affecting users.

### What this PR does

Two changes that need to land together:

1. **`android/gradle.properties.release`** — remove `x86_64` from
`reactNativeArchitectures` so React Native's own native libs (Hermes, RN
core) aren't built for that ABI in the production AAB.

2. **`android/app/build.gradle`** — add
`ndk.abiFilters(*reactNativeArchitectures())` inside `defaultConfig`.
Without this, AGP packages every ABI shipped in dependency AARs
regardless of `reactNativeArchitectures`, which is what put the x86_64
`libconceal.so` and `libsecp256k1.so` into the AAB in the first place.

A small refactor of the `reactNativeArchitectures()` helper to return a
`List` (via `.toList()`) makes the Groovy spread
`*reactNativeArchitectures()` into `abiFilters(String...)` robust across
AGP/Groovy versions.

### What this PR does NOT change

- `android/gradle.properties` (default) — local dev still gets x86_64
emulator support.
- `android/gradle.properties.github` (CI E2E, `x86_64` only) — the new
`abiFilters` resolves to `["x86_64"]` in that context, so emulator tests
continue to work.
- `android/gradle.properties.github.dual-versions`
(`armeabi-v7a,arm64-v8a`) — already excludes x86_64.
- `scripts/build.sh` `-PreactNativeArchitectures=...` CLI overrides —
still respected by the helper.

### Follow-up work (out of scope)

The same `.so` files still ship for `arm64-v8a` with 4 KB alignment.
If/when Play extends the warning to arm64, or enforces the runtime 16 KB
device requirement more aggressively on Android 15+ devices in 16 KB
mode, these will need structural fixes:

- Replace `react-native-fast-crypto` with
`react-native-quick-crypto@1.x` (we only consume `scrypt`).
- Drop the Conceal dependency from `react-native-keychain` (yarn patch
or upgrade to v10) — safe because `minSdk = 24` means the Conceal cipher
path is never selected at runtime.

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Fixes: Google Play 16 KB page-size warning for
`base/lib/x86_64/libconceal.so` and `base/lib/x86_64/libsecp256k1.so`.

## **Manual testing steps**

```gherkin
Feature: Production AAB no longer contains x86_64 ABI

  Scenario: A production release build is generated
    Given a clean checkout of this branch
    When the production AAB is built via the standard release workflow
      (the same one that does `cp android/gradle.properties.release android/gradle.properties`
       before `./gradlew bundleProdRelease`)
    Then `unzip -l app-prod-release.aab | grep '/lib/'` shows only
      `lib/arm64-v8a/`, `lib/armeabi-v7a/`, and `lib/x86/` entries
    And no `lib/x86_64/` entries appear in the output
    And no `libconceal.so` or `libsecp256k1.so` files appear under any
      `lib/x86_64/` path

  Scenario: Local dev on x86_64 emulator still works
    Given a developer running `yarn android` on an Intel Mac with an x86_64 emulator
    When the debug variant is built using the default `android/gradle.properties`
    Then x86_64 native libs are still produced and the app installs and runs

  Scenario: CI E2E on x86_64 emulator still works
    Given CI overlays `android/gradle.properties.github` (reactNativeArchitectures=x86_64)
    When `./gradlew assembleProdDebug` runs
    Then the resulting APK contains a `lib/x86_64/` directory and Detox tests pass
```

## **Screenshots/Recordings**

### **Before**

Play Console flagged:
- `base/lib/x86_64/libconceal.so`
- `base/lib/x86_64/libsecp256k1.so`

### **After**

Production AAB contains no `lib/x86_64/` directory — both warnings
cleared.

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [ ] I've included tests if applicable — N/A (build-config change;
verification is via the `unzip -l` check on the produced AAB)
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable — N/A
- [ ] I've applied the right labels on the PR — `team-mobile-platform`

#### Performance checks (if applicable)

- [x] I've tested on Android — verified locally that the abiFilters
change does not break the `gradle.properties.github` (x86_64-only) path
used by CI E2E.
- [ ] I've tested with a power user scenario — N/A (no JS behavior
change)
- [ ] I've instrumented key operations with Sentry traces — N/A

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

Made with [Cursor](https://cursor.com)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Build-time ABI and packaging configuration only; no runtime app logic,
auth, or data-path changes, with emulator/CI paths still driven by their
gradle overlays.
> 
> **Overview**
> Production Play Store builds stop shipping **x86_64** native libraries
so Google Play’s **16 KB page-size** warnings go away for prebuilt
`libconceal.so` and `libsecp256k1.so` that only appeared under
`lib/x86_64/`.
> 
> **`android/gradle.properties.release`** drops `x86_64` from
`reactNativeArchitectures` (now `armeabi-v7a`, `arm64-v8a`, `x86`).
**`android/app/build.gradle`** adds
`defaultConfig.ndk.abiFilters(*reactNativeArchitectures())` so AGP does
not still package every ABI from third-party AARs when RN’s arch list is
narrower. The `reactNativeArchitectures()` helper now returns a Groovy
`List` via `.toList()` so the spread into `abiFilters` is reliable.
> 
> Default and CI gradle property files are unchanged in this diff: local
debug can still target x86_64 emulators, and CI E2E overlays that still
set `x86_64` only continue to filter to that ABI.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
40dd023. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Cursor <cursoragent@cursor.com>
…m sheet closes order screen (#30561)

## **Description**

Fixes a bug where closing the order type bottom sheet (by tapping
outside, pressing the close button, or selecting an option) would also
close the entire order screen.

**Root cause**: `PerpsOrderTypeBottomSheet` passed
`shouldNavigateBack={!externalSheetRef}` to the `BottomSheet` component.
When embedded in `PerpsOrderView` (no external sheet ref), this
evaluated to `true`, causing the BottomSheet infrastructure to call
`navigation.goBack()` on dismiss — popping the order screen from the
navigation stack. The parent's `onClose` callback then fired
redundantly.

**Fix**: Set `shouldNavigateBack={false}` unconditionally and always
pass the `onClose` prop through to BottomSheet, so the parent controls
what happens on dismiss.

## **Changelog**

CHANGELOG entry: Fixed order type bottom sheet dismissal incorrectly
closing the order screen

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/TAT-3113

## **Manual testing steps**

```gherkin
Feature: Order type bottom sheet dismissal

  Scenario: User selects an order type from the bottom sheet
    Given the user is on the perps order screen with the order type bottom sheet open

    When user taps on "Market" or "Limit" order type
    Then the bottom sheet closes
    And the order screen remains visible and functional

  Scenario: User reopens the order type bottom sheet after dismissal
    Given the user just dismissed the order type bottom sheet

    When user taps the order type button in the order header
    Then the order type bottom sheet opens again
    And the user can select a different order type
```

## **Screenshots/Recordings**

### **Before**

Order type sheet dismissal (tap outside or select option) closes the
entire order screen — user is navigated back to market details.


https://github.com/user-attachments/assets/a8a36f0a-6be5-418b-8c95-550bd4dc8056


### **After**

Order type sheet dismissal keeps the order screen open. User can reopen
the sheet and select a different order type.


https://github.com/user-attachments/assets/d2d18a62-42fa-433b-8ba5-812e01d19f1d


## **Validation Recipe**
<details><summary>recipe.json (18 steps — order type sheet
open/dismiss/reopen cycle)</summary>

```json
{
  "schema_version": 1,
  "title": "TAT-3113: Order type bottom sheet dismissal does not close order screen",
  "jira": "TAT-3113",
  "description": "Verifies that dismissing the order type bottom sheet (by selecting an option) keeps the user on the order screen instead of navigating back.",
  "acceptance_criteria": [
    "AC1: Selecting an order type from the bottom sheet closes the sheet but does NOT close the order screen",
    "AC2: After dismissal, the order form remains visible and functional (can reopen the sheet)"
  ],
  "validate": {
    "workflow": {
      "pre_conditions": ["wallet.unlocked", "perps.ready_to_trade"],
      "entry": "nav-to-details",
      "nodes": {
        "nav-to-details": {
          "action": "navigate",
          "target": "PerpsMarketDetails",
          "params": { "market": { "symbol": "BTC" } },
          "next": "wait-details-loaded"
        },
        "wait-details-loaded": {
          "action": "wait_for",
          "test_id": "perps-market-details-long-button",
          "timeout_ms": 8000,
          "next": "open-order"
        },
        "open-order": {
          "action": "press",
          "test_id": "perps-market-details-long-button",
          "next": "wait-order-form"
        },
        "wait-order-form": {
          "action": "wait_for",
          "test_id": "perps-order-header-order-type-button",
          "timeout_ms": 5000,
          "next": "open-order-type-sheet"
        },
        "open-order-type-sheet": {
          "action": "press",
          "test_id": "perps-order-header-order-type-button",
          "next": "wait-sheet"
        },
        "wait-sheet": {
          "action": "wait_for",
          "test_id": "perps-order-type-market",
          "timeout_ms": 3000,
          "next": "screenshot-sheet-open"
        },
        "screenshot-sheet-open": {
          "action": "screenshot",
          "filename": "evidence-order-type-sheet-open.png",
          "note": "Order type bottom sheet is open showing Market and Limit options",
          "next": "dismiss-select-market"
        },
        "dismiss-select-market": {
          "action": "press",
          "test_id": "perps-order-type-market",
          "next": "wait-dismiss"
        },
        "wait-dismiss": {
          "action": "wait",
          "duration_ms": 1000,
          "next": "verify-still-on-order"
        },
        "verify-still-on-order": {
          "action": "eval_sync",
          "expression": "(function(){ var r = globalThis.__AGENTIC__.getRoute(); return JSON.stringify({ route: r.name }); })()",
          "assert": { "operator": "eq", "field": "route", "value": "RedesignedConfirmations" },
          "next": "verify-order-form-visible"
        },
        "verify-order-form-visible": {
          "action": "eval_sync",
          "expression": "(function(){ var el = globalThis.__AGENTIC__.findFiberByTestId('perps-order-view-place-order-button'); return JSON.stringify({ visible: !!el }); })()",
          "assert": { "operator": "eq", "field": "visible", "value": true },
          "next": "screenshot-after-dismiss"
        },
        "screenshot-after-dismiss": {
          "action": "screenshot",
          "filename": "evidence-order-still-visible.png",
          "note": "Order form still visible after selecting order type — order screen was NOT closed",
          "next": "reopen-sheet"
        },
        "reopen-sheet": {
          "action": "press",
          "test_id": "perps-order-header-order-type-button",
          "next": "wait-reopen"
        },
        "wait-reopen": {
          "action": "wait_for",
          "test_id": "perps-order-type-limit",
          "timeout_ms": 3000,
          "next": "select-limit"
        },
        "select-limit": {
          "action": "press",
          "test_id": "perps-order-type-limit",
          "next": "wait-dismiss-2"
        },
        "wait-dismiss-2": {
          "action": "wait",
          "duration_ms": 1000,
          "next": "verify-still-on-order-2"
        },
        "verify-still-on-order-2": {
          "action": "eval_sync",
          "expression": "(function(){ var r = globalThis.__AGENTIC__.getRoute(); return JSON.stringify({ route: r.name }); })()",
          "assert": { "operator": "eq", "field": "route", "value": "RedesignedConfirmations" },
          "next": "screenshot-final"
        },
        "screenshot-final": {
          "action": "screenshot",
          "filename": "evidence-limit-selected-still-on-order.png",
          "note": "After selecting Limit order type, order form still visible with limit type active",
          "next": "done"
        },
        "done": {
          "action": "end",
          "status": "pass"
        }
      }
    }
  }
}
```
</details>

## **Validation Logs**
Command:
```bash
IOS_SIMULATOR=mm-3 bash scripts/perps/agentic/validate-recipe.sh .task/feat/tat-3113-0521-165010/artifacts/ --skip-manual
```

<details><summary>Full output (18/18 passed)</summary>

```
Running recipe: TAT-3113: Order type bottom sheet dismissal does not close order screen
Pre-conditions: wallet.unlocked, perps.ready_to_trade
Workflow nodes: 19

Pre-conditions: PASS

[nav-to-details] navigate to PerpsMarketDetails — PASS
[wait-details-loaded] wait for perps-market-details-long-button — PASS
[open-order] press perps-market-details-long-button — PASS
[wait-order-form] wait for perps-order-header-order-type-button — PASS
[open-order-type-sheet] press perps-order-header-order-type-button — PASS
[wait-sheet] wait for perps-order-type-market — PASS
[screenshot-sheet-open] screenshot evidence-order-type-sheet-open.png — PASS
[dismiss-select-market] press perps-order-type-market — PASS
[wait-dismiss] wait 1000ms — PASS
[verify-still-on-order] eval_sync route=RedesignedConfirmations — PASS
[verify-order-form-visible] eval_sync visible=true — PASS
[screenshot-after-dismiss] screenshot evidence-order-still-visible.png — PASS
[reopen-sheet] press perps-order-header-order-type-button — PASS
[wait-reopen] wait for perps-order-type-limit — PASS
[select-limit] press perps-order-type-limit — PASS
[wait-dismiss-2] wait 1000ms — PASS
[verify-still-on-order-2] eval_sync route=RedesignedConfirmations — PASS
[screenshot-final] screenshot evidence-limit-selected-still-on-order.png — PASS

Results: 18/18 passed
Recipe: PASS
```
</details>

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Single-component BottomSheet prop change for a nested sheet; no auth,
payments, or shared infrastructure changes.
> 
> **Overview**
> Fixes perps order flow navigation so dismissing the **order type**
bottom sheet no longer pops the whole order screen.
> 
> `PerpsOrderTypeBottomSheet` now always passes
`shouldNavigateBack={false}` to `BottomSheet` and always wires
`onClose`, instead of enabling `navigation.goBack()` when no external
`sheetRef` is provided. Dismissal (tap outside, close, or picking
Market/Limit) only runs the parent `onClose` handler and leaves the user
on the order form.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
77e3642. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

Adds [agent-device](https://github.com/callstackincubator/agent-device)
as a dev dependency, exposing it as a CLI for AI agents to control
iOS/Android simulators during development.

Device control (opening the app, navigating screens, taking snapshots,
interacting with UI elements, capturing visual evidence) runs through:

```bash
yarn agent-device <command> --json
```

The package is installed locally so no global install is required.
Version is pinned to an exact version (`0.14.8`, no semver range) as
recommended by the security team — combined with Yarn's lockfile
checksum, this prevents undetected same-tag re-deployments.

Ideally to be used with the [simulator-control
skill](Consensys/skills#11)

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/MCWP-450

## **Manual testing steps**

```gherkin
Feature: agent-device CLI availability

  Background:
    Given the branch is checked out and `yarn install` has been run
    And the simulator-control skill is installed via `yarn skills --domain testing`
    And an iOS simulator is booted
    And Metro is running via `yarn watch:clean`

  Scenario: AI agent controls the iOS simulator via CLI
    Given the Cursor IDE is open on this project
    When the user sends the following prompt to the Cursor agent:
      """
      Open the MetaMask app on the iOS simulator and take a screenshot of the home screen
      """
    And the agent runs `yarn agent-device devices --platform ios` to list booted simulators
    And the agent runs `yarn agent-device open io.metamask.MetaMask --platform ios`
    And the MetaMask app opens on the simulator
    And the agent runs `yarn agent-device screenshot` and returns the image
```

## **Screenshots/Recordings**

### **Before**

N/A

### **After**

<img width="500" alt="image"
src="https://github.com/user-attachments/assets/c6e06db5-4cef-44fc-ab79-b1d66883e32e"
/>

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

#### Performance checks (if applicable)

- [x] I've tested on Android
- [x] I've tested with a power user scenario
- [x] I've instrumented key operations with Sentry traces for production
performance metrics

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: adds a pinned dev-only CLI dependency plus lockfile updates
and documentation, with no runtime/app logic changes; main concern is
install friction due to `agent-device`'s Node engine declaration.
> 
> **Overview**
> Adds the `agent-device` package (pinned to `0.14.8`) as a dev
dependency to provide a local `yarn agent-device` CLI for controlling
iOS/Android simulators.
> 
> Updates `yarn.lock` for the new dependency and related transitive
bumps (notably `fast-xml-parser`/`fast-xml-builder`), adds
`agent-device` to `.depcheckrc.yml` ignores since it’s CLI-only, and
documents usage in `docs/readme/agent-device.md` including the Node
engine note.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7c9637f. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

Adds toast notifications for Money Account deposit and withdrawal
transactions, mirroring the existing Earn (`useEarnToasts` /
`useMusdConversionStatus`) pattern.

A new `useMoneyTransactionStatus` hook subscribes to
`TransactionController:transactionStatusUpdated` and
`transactionConfirmed`, filters for
`TransactionType.moneyAccountDeposit` and
`TransactionType.moneyAccountWithdraw`, deduplicates by transaction id +
status, and surfaces:

- `approved` → "Transaction in progress" (spinner, persistent)
- `confirmed` → "Transaction complete" with the decoded mUSD amount
formatted as fiat. Falls back to a `X.XX mUSD` label when no fiat rate
is available so the toast still surfaces a real value.
- `failed` → "Transaction failed" with a DSRN Primary "Try again"
button. The button navigates the user to the relevant Money picker sheet
(Add money / Transfer) so they can re-initiate; a true retry that
preserves the prior amount/token is tracked as a follow-up.

The hook is mounted globally via `<MoneyTransactionMonitor />` placed
alongside `<EarnTransactionMonitor />` in `Nav/Main/index.js` so toasts
surface even after the user has navigated away from Money screens. Retry
navigation goes through `NavigationService` (not `useNavigation`)
because the hook runs outside the `MainNavigator`'s screen scope.

Active transaction types covered today: `moneyAccountDeposit` (Convert
crypto + Move mUSD) and `moneyAccountWithdraw` (Between accounts). Out
of scope: Ramp "Deposit funds" purchases (no `moneyAccountDeposit`
transaction is dispatched) and Perps / Predict transfers (currently
"Under construction" stubs; a TODO in `useMoneyTransactionStatus.ts`
marks where to derive the withdraw success destination once they ship).

## **Changelog**

CHANGELOG entry: Added in-app toasts for Money Account deposit and
withdrawal transactions, including a "Try again" action when a
transaction fails.

## **Related issues**

Fixes:
[MUSD-810](https://consensyssoftware.atlassian.net/browse/MUSD-810)

## **Manual testing steps**

```gherkin
Feature: Money Account transaction toasts

  Scenario: User completes a Money Account deposit
    Given the user is on the Money home screen with a non-zero source balance
    When the user taps "Add money" → "Convert crypto" and confirms the conversion
    Then a "Transaction in progress" toast appears with a spinner
    And when the transaction confirms, a "Transaction complete" toast appears
    And the body reads "{amount} added to Money account."

  Scenario: User completes a Money Account withdrawal
    Given the user has a non-zero Money Account balance
    When the user taps "Transfer" → "Between accounts" and confirms
    Then a "Transaction in progress" toast appears with a spinner
    And when the transaction confirms, a "Transaction complete" toast appears
    And the body reads "{amount} moved to Between accounts."

  Scenario: A deposit transaction fails
    Given the user has confirmed a deposit
    When the transaction fails on-chain
    Then a "Transaction failed" toast appears with a "Try again" button
    And tapping "Try again" navigates to the Add money picker sheet

  Scenario: A withdrawal transaction fails
    Given the user has confirmed a withdrawal
    When the transaction fails on-chain
    Then a "Transaction failed" toast appears with a "Try again" button
    And tapping "Try again" navigates to the Transfer picker sheet
```

## **Screenshots/Recordings**

### **Before**

### **After**

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

#### Performance checks (if applicable)

- [ ] I've tested on Android
- [x] I've tested with a power user scenario
- [x] I've instrumented key operations with Sentry traces for production
performance metrics

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.


[MUSD-810]:
https://consensyssoftware.atlassian.net/browse/MUSD-810?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds global transaction event listeners and suppresses existing in-app
notifications for `moneyAccountDeposit`/`moneyAccountWithdraw`, which
could impact user-facing transaction feedback and requires careful
validation across transaction lifecycles (including batched
transactions).
> 
> **Overview**
> **Money Account deposits/withdrawals now surface in-app toasts** for
`approved` (deferred in-progress), `confirmed` (success with
decoded/fiat-formatted mUSD amount), and `failed` (error) transaction
states via a new `useMoneyTransactionStatus` hook and
`MoneyTransactionMonitor` mounted in `Nav/Main`.
> 
> This introduces a new `useMoneyToasts` builder for consistent toast UI
+ haptics and adds corresponding i18n strings, while also updating
`NotificationManager` to *skip* legacy transaction notifications for
`moneyAccountDeposit` and `moneyAccountWithdraw` and exporting
`TELLER_ABI` for calldata decoding. Tests were added to cover toast
option building, event subscription/dedup/timer cleanup, and
batch/nested transaction handling.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
197e27e. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…#30580)

<!--
Please submit this PR as a draft initially.

Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.

In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->

## **Description**

Re-run CI on finished runs when e2e labels are modified in the PR

<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->

## **Changelog**

<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`

If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`

(This helps the Release Engineer do their job more quickly and
accurately)
-->

CHANGELOG entry:

## **Related issues**

Fixes:

## **Manual testing steps**

```gherkin
Feature: my feature name

  Scenario: user [verb for user action]
    Given [describe expected initial app state]

    When user [verb for user action]
    Then [describe expected outcome]
```

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

<!--
Every checklist item must be consciously assessed before marking this PR
as
"Ready for review". A checked box means you deliberately considered that
responsibility, not that you literally performed every action listed.

Unchecked boxes are ambiguous: they are not an implicit "N/A" and they
are not
a silent "skip". See `docs/readme/ready-for-review.md` for the full
checklist
semantics.
-->

- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I've included tests if applicable
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).

## **Pre-merge reviewer checklist**

<!--
Reviewer checklist items follow the same semantics as the author
checklist: an
unchecked box is ambiguous, a checked box means the reviewer consciously
assessed that responsibility. See `docs/readme/ready-for-review.md`.
-->

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Changes only GitHub Actions orchestration for label-triggered CI
reruns; no app, auth, or data paths.
> 
> **Overview**
> Tightens the **rerun CI on E2E skip label** workflow so cancellation
and rerun behave correctly when multiple `ci.yml` runs exist on the
branch.
> 
> The **wait-for-cancel** step no longer tracks a single run ID from the
find step. It polls `gh run list` for any `ci.yml` runs on the head
branch that are still `in_progress` or `queued`, and only proceeds when
that count hits zero (or times out after 600s).
> 
> The **rerun** step now runs only when the pull request is **open** and
a latest run ID was found. Rerun is invoked directly without swallowing
failures, so a non-retriable run surfaces as a workflow error instead of
a soft log message.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
cc82224. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@pull pull Bot locked and limited conversation to collaborators May 25, 2026
@pull pull Bot added the ⤵️ pull label May 25, 2026
@pull pull Bot merged commit af1f61f into Reality2byte:main May 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants